/*
 * Copyright (c) 2017-present, CV4J Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cv4j.core.hist;

import com.cv4j.core.datamodel.ColorProcessor;
import com.cv4j.core.datamodel.ImageProcessor;
import com.cv4j.image.util.Tools;

/**
 * calc直方图
 *
 * @author dev
 * @date 2021/07/22
 */
public class CalcHistogram {
    /**
     * rgb颜色
     */
    public static final int COLOR_RGB = 1;
    /**
     * hsv颜色
     */
    public static final int COLOR_HSV = 2;

    /**
     * 计算规范
     *
     * @param src src
     * @param bins bins
     * @return {@link int[]}
     */
    public static int[] calculateNormHist(ImageProcessor src, int bins) {
        int width = src.getWidth();
        int height = src.getHeight();
        int len = width * height;
        byte[] red = ((ColorProcessor) src).getRed();
        byte[] green = ((ColorProcessor) src).getGreen();
        byte[] blue = ((ColorProcessor) src).getBlue();
        int level = 256 / bins;
        int[] hist = new int[bins * bins * bins];
        int r1 = 0;
        int g1 = 0;
        int b1 = 0;
        int index = 0;
        for (int i = 0; i < len; i++) {
            r1 = red[i] & 0xff;
            g1 = green[i] & 0xff;
            b1 = blue[i] & 0xff;
            index = (r1 / level) + (g1 / level) * bins + (b1 / level) * bins * bins;
            hist[index]++;
        }
        return hist;
    }

    /**
     * calc rgbhist
     *
     * @param src src
     * @param bins bins
     * @param hist hist
     * @param norm 规范
     */
    public void calcRGBHist(ImageProcessor src, int bins, int[][] hist, boolean norm) {
        if (src == null) {
            return;
        }
        int numChannels = src.getChannels();
        for (int i = 0; i < numChannels; i++) {
            byte[] data = src.toByte(i);
            hist[i] = getHistogram(data, bins, new int[]{0, 256});
        }
        if (!norm) {
            return;
        }

        float min = 10000000;
        float max = 0;
        float delta;
        for (int i = 0; i < numChannels; i++) {
            for (int j = 0; j < bins; j++) {
                min = Math.min(hist[i][j], min);
                max = Math.max(hist[i][j], max);
            }
            delta = max - min;
            for (int j = 0; j < bins; j++) {
                hist[i][j] = (int) (((hist[i][j] - min) / delta) * 255);
            }
        }
    }

    /**
     * calc hsvhist
     *
     * @param src src
     * @param bins bins
     * @param hist hist
     * @param norm 规范
     */
    public void calcHSVHist(ImageProcessor src, int bins, int[][] hist, boolean norm) {
        calcHSVHist(src, bins, hist, norm, new int[][]{{0, 180}, {0, 256}, {0, 256}});
    }

    /**
     * calc hsvhist
     *
     * @param src src
     * @param bins bins
     * @param hist hist
     * @param norm 规范
     * @param ranges 范围
     */
    public void calcHSVHist(ImageProcessor src, int bins, int[][] hist, boolean norm, int[][] ranges) {
        if (src == null) {
            return;
        }
        if (src.getChannels() == 1) {
            calcRGBHist(src, bins, hist, norm);
            return;
        }
        int width = src.getWidth();
        int height = src.getHeight();
        byte[][] hsv = new byte[3][width * height];
        byte[] red = ((ColorProcessor) src).getRed();
        byte[] green = ((ColorProcessor) src).getGreen();
        byte[] blue = ((ColorProcessor) src).getBlue();
        Tools.rgb2hsv(new byte[][]{red, green, blue}, hsv);
        for (int i = 0; i < 3; i++) {
            byte[] data = hsv[i];
            hist[i] = getHistogram(data, bins, ranges[i]);
        }
        if (!norm) {
            return;
        }

        float min = 10000000;
        float max = 0;
        float delta;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < bins; j++) {
                min = Math.min(hist[i][j], min);
                max = Math.max(hist[i][j], max);
            }
            delta = max - min;
            int dr = ranges[i][1] - ranges[i][0];
            for (int j = 0; j < bins; j++) {
                hist[i][j] = (int) (((hist[i][j] - min) / delta) * dr);
            }
        }
    }

    /**
     * 得到的直方图
     *
     * @param data 数据
     * @param bins bins
     * @param range 范围
     * @return {@link int[]}
     */
    private int[] getHistogram(byte[] data, int bins, int[] range) {
        int dr = range[1] - range[0];
        int[] hist = new int[dr];
        int length = data.length;
        for (int i = 0; i < length; i++) {
            hist[data[i] & 0xff]++;
        }

        double numOfGap = dr / bins;
        int[] wh = new int[bins];

        double prebin;
        double currbin;
        double w1;
        double w2;
        for (int k = 0; k < bins; k++) {
            prebin = (k - 1) * numOfGap;
            currbin = k * numOfGap;
            int obin = (int) Math.floor(prebin);
            if (obin < 0) {
                obin = 0;
                prebin = 0;
            }
            int nbin = (int) Math.floor(currbin);

            for (int j = obin; j <= nbin; j++) {
                wh[k] += hist[j];
            }

            w1 = prebin - obin;
            w2 = currbin - nbin;
            if (w1 > 0 && w1 < 1) {
                wh[k] = (int) (wh[k] - hist[obin] * w1);
            }
            if (w2 > 0 && w2 < 1) {
                wh[k] = (int) (wh[k] + hist[nbin + 1] * w2);
            }
        }
        return wh;
    }

}
